#include "Components/AutoShape/AutoShapeConfigBox.h"

#include "Components/AutoShape/AutoShapeComponent.h"
#include "PhysicsEngine/BodySetup.h"
#include "PhysicsEngine/BoxElem.h"

template <EShapeBodySetupHelper UpdateBodySetupAction, typename BodySetupType>
bool InvalidateOrUpdateBoxBodySetup(BodySetupType& ShapeBodySetup, bool bUseArchetypeBodySetup, FVector BoxExtent)
{
	check((bUseArchetypeBodySetup && UpdateBodySetupAction == EShapeBodySetupHelper::InvalidateSharingIfStale) || (!bUseArchetypeBodySetup && UpdateBodySetupAction == EShapeBodySetupHelper::UpdateBodySetup));
	check(ShapeBodySetup->AggGeom.BoxElems.Num() == 1);
	FKBoxElem* se = ShapeBodySetup->AggGeom.BoxElems.GetData();

	// @todo do we allow this now?
	// check for malformed values
	if (BoxExtent.X < UE_KINDA_SMALL_NUMBER)
	{
		BoxExtent.X = 1.0f;
	}

	if (BoxExtent.Y < UE_KINDA_SMALL_NUMBER)
	{
		BoxExtent.Y = 1.0f;
	}

	if (BoxExtent.Z < UE_KINDA_SMALL_NUMBER)
	{
		BoxExtent.Z = 1.0f;
	}

	float XExtent = BoxExtent.X * 2.f;
	float YExtent = BoxExtent.Y * 2.f;
	float ZExtent = BoxExtent.Z * 2.f;

	if (UpdateBodySetupAction == EShapeBodySetupHelper::UpdateBodySetup)
	{
		// now set the PhysX data values
		se->SetTransform(FTransform::Identity);
		se->X = XExtent;
		se->Y = YExtent;
		se->Z = ZExtent;
	}
	else if (se->X != XExtent || se->Y != YExtent || se->Z != ZExtent)
	{
		ShapeBodySetup = nullptr;
		bUseArchetypeBodySetup = false;
	}

	return bUseArchetypeBodySetup;
}

FPrimitiveSceneProxy* UAutoShapeConfigBox::CreateSceneProxy(UAutoShapeComponent* Owner)
{
	return Owner ? new FKBoxSceneProxy(Owner, this) : Super::CreateSceneProxy(Owner);
}

bool UAutoShapeConfigBox::IsZeroExtent(const UAutoShapeComponent* Owner) const
{
	return BoxExtent.IsZero();
}

struct FCollisionShape UAutoShapeConfigBox::GetCollisionShape(const UAutoShapeComponent* Owner, float Inflation) const
{
	FVector Extent = GetScaledBoxExtent(Owner) + Inflation;
	if (Inflation < 0.f)
	{
		// Don't shrink below zero size.
		Extent = Extent.ComponentMax(FVector::ZeroVector);
	}

	return FCollisionShape::MakeBox(Extent);
}

FBoxSphereBounds UAutoShapeConfigBox::CalcBounds(const UAutoShapeComponent* Owner, const FTransform& LocalToWorld) const
{
	return FBoxSphereBounds(FBox(-BoxExtent, BoxExtent)).TransformBy(LocalToWorld);
}

void UAutoShapeConfigBox::UpdateBodySetup(UAutoShapeComponent* Owner)
{
	if (Owner)
	{
		if (Owner->ShapeBodySetup && Owner->ShapeBodySetup->AggGeom.BoxElems.Num() != 1)
		{
			Owner->ShapeBodySetup = nullptr;
			Owner->SetbUseArchetypeBodySetup(false);
		}

		if (Owner->InPrepareSharedBodySetup())
		{
			Owner->SetbUseArchetypeBodySetup(InvalidateOrUpdateBoxBodySetup<EShapeBodySetupHelper::InvalidateSharingIfStale>(Owner->ShapeBodySetup, Owner->GetbUseArchetypeBodySetup(), BoxExtent));
		}

		Owner->InCreateShapeBodySetupIfNeeded<FKBoxElem>();

		if (!Owner->GetbUseArchetypeBodySetup())
		{
			InvalidateOrUpdateBoxBodySetup<EShapeBodySetupHelper::UpdateBodySetup>(Owner->ShapeBodySetup, Owner->GetbUseArchetypeBodySetup(), BoxExtent);
		}
	}
	else
	{
		Super::UpdateBodySetup(Owner);
	}
}

void UAutoShapeConfigBox::SetBoxExtent(UAutoShapeComponent* Owner, FVector InBoxExtent, bool bUpdateOverlaps)
{
	if (Owner)
	{
		BoxExtent = InBoxExtent;
		Owner->UpdateBounds();
		Owner->MarkRenderStateDirty();
		Owner->UpdateBodySetup();

		// do this if already created
		// otherwise, it hasn't been really created yet
		if (Owner->IsPhysicsStateCreated())
		{
			// Update physics engine collision shapes
			Owner->BodyInstance.UpdateBodyScale(Owner->GetComponentTransform().GetScale3D(), true);

			if (bUpdateOverlaps && Owner->IsCollisionEnabled() && Owner->GetOwner())
			{
				Owner->UpdateOverlaps();
			}
		}
	}
	else
	{
		Super::UpdateBodySetup(Owner);
	}
}
